<template>
  <view class="sv-tiptap">
    <slot name="header"></slot>
    <view class="sv-tiptap-container" :id="eid"></view>
    <slot name="footer"></slot>

    <!-- renderjs监听器 -->
    <text
      :eid="eid"
      :change:eid="tiptapEditor.idObserver"
      :destroyFlag="destroyFlag"
      :change:destroyFlag="tiptapEditor.destroyObserver"
      :cmdFlag="cmdFlag"
      :change:cmdFlag="tiptapEditor.cmdObserver"
    ></text>
  </view>
</template>

<script>
export default {
  props: {
    eid: {
      type: String,
      default: 'sv-tiptap-editor'
    },
    toolbar: {
      type: Boolean,
      default: true
    },
    modelValue: {
      type: Object,
      default: () => {}
    }
  },
  emits: [
    'onBeforeCreate',
    'onCreate',
    'onUpdate',
    'onSelectionUpdate',
    'onTransaction',
    'onFocus',
    'onBlur',
    'onDestroy',
    'onPaste',
    'onDrop',
    'onContentError',
    'onCmdCallback',
    'update:modelValue'
  ],
  data() {
    return {
      destroyFlag: 0,
      cmdFlag: null
    }
  },
  computed: {},
  watch: {},
  mounted() {},
  beforeUnmount() {
    this.toDestroy()
  },
  methods: {
    // ======= tiptap生命周期 =======
    onBeforeCreate(e) {
      this.$emit('onBeforeCreate', e)
    },
    onCreate(e) {
      this.$emit('onCreate', e)
    },
    onUpdate(e) {
      this.$emit('update:modelValue', e)
      this.$emit('onUpdate', e)
    },
    onSelectionUpdate(e) {
      this.$emit('onSelectionUpdate', e)
    },
    onTransaction(e) {
      this.$emit('onTransaction', e)
    },
    onFocus(e) {
      this.$emit('onFocus', e)
    },
    onBlur(e) {
      this.$emit('onBlur', e)
    },
    onDestroy(e) {
      this.$emit('onDestroy', e)
    },
    onPaste(e) {
      this.$emit('onPaste', e)
    },
    onDrop(e) {
      this.$emit('onDrop', e)
    },
    onContentError(e) {
      this.$emit('onContentError', e)
    },
    onCmdCallback(e) {
      this.$emit('onCmdCallback', e)
    },
    // ======= 方法 =======
    toDestroy() {
      this.destroyFlag++
    },
    cmd(name, value) {
      setTimeout(() => {
        this.cmdFlag = { name, value }
      })
    },
    cmdReset(e) {
      this.cmdFlag = null
    }
  }
}
</script>

<script module="tiptapEditor" lang="renderjs">
import { Editor } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
import ExtensionHeading from "@tiptap/extension-heading";
import ExtensionCharacterCount from "@tiptap/extension-character-count";
import ExtensionImageResize from 'tiptap-extension-resize-image';
import ExtensionVideo from "tiptap-basic-video";
import ExtensionLink from "@tiptap/extension-link";
import TaskQueue from '../common/queue.js';

export default {
  data() {
    return {
      _eid: '',
      _ctx: null,
      _cmd: null,
      taskQueue: new TaskQueue()
    }
  },
  methods: {
    idObserver(newValue, oldValue, ownerInstance, instance) {
      if (newValue) {
        this._eid = newValue
        this.ready(ownerInstance)
      }
    },
    destroyObserver(newValue, oldValue, ownerInstance, instance) {
      if (newValue) {
        this._ctx?.destroy()
        this._ctx = null
        this._cmd = null
      }
    },
    cmdObserver(newValue, oldValue, ownerInstance, instance) {
      if (newValue) {
        this.taskQueue.addTask(() => {
          if (this._cmd[newValue.name]) {
            this._cmd[newValue.name](newValue.value)
            ownerInstance.callMethod('onCmdCallback', newValue)
          } else {
            console.warn(`cmd: ${newValue.name} not found`)
          }
          // 重置指令
          ownerInstance.callMethod('cmdReset', newValue)
        })
      }
    },
    ready(ownerInstance) {
      const el = document.querySelector(`#${this._eid}`)
      if(!el) return console.warn(`el#${this._eid} not found`)

      // 插件配置
      const Heading = ExtensionHeading
      const Link = ExtensionLink
      const CharacterCount = ExtensionCharacterCount.configure({ limit: null })
      const Image = ExtensionImageResize.configure({
        inline: true,
        allowBase64: true,
        HTMLAttributes: {
          class: "tiptap-image-custom-class",
        }
      })
      const Video = ExtensionVideo.configure({
        height: "auto",
        width: "auto",
        inline: true,
        controls: true,
        autoplay: false,
        HTMLAttributes: {
          class: "tiptap-video-custom-class" ,
        }
      })

      new Editor({
        element: el,
        extensions: [
          StarterKit,
          Heading,
          Link,
          CharacterCount,
          Image,
          Video,
        ],
        onBeforeCreate: ({ editor }) => {
          // 在视图创建之前
          ownerInstance.callMethod('onBeforeCreate', { id: this._eid })
        },
        onCreate: ({ editor }) => {
          // 编辑器已准备好
          this._ctx = editor
          ownerInstance.callMethod('onCreate', { id: this._eid })
        },
        onUpdate: ({ editor, transaction }) => {
          // 内容已更改
          ownerInstance.callMethod('onUpdate', {
            id: this._eid,
            transaction,
            html: editor.getHTML(),
            text: editor.getText(),
            json: editor.getJSON(),
            characters: editor.storage.characterCount.characters()
          })
        },
        onSelectionUpdate: ({ editor, transaction }) => {
          // 选择范围已更改
          ownerInstance.callMethod('onSelectionUpdate', { id: this._eid, transaction })
        },
        onTransaction: ({ editor, transaction }) => {
          // 编辑器状态已改变，有必要时需要及时更新相关状态，否则会报错：Applying a mismatched transaction
          this._ctx = editor
          this._cmd = editor.commands
          ownerInstance.callMethod('onTransaction', { id: this._eid, transaction })
        },
        onFocus: ({ editor, event, transaction }) => {
          // 编辑器获得焦点
          ownerInstance.callMethod('onFocus', { id: this._eid, event, transaction })
        },
        onBlur: ({ editor, event, transaction }) => {
          // 编辑器失去焦点
          ownerInstance.callMethod('onBlur', { id: this._eid, event, transaction })
        },
        onDestroy: () => {
          // 编辑器正在销毁
          ownerInstance.callMethod('onDestroy', { id: this._eid })
        },
        onPaste: (e) => {
          // 编辑器内容粘贴
          const clipboardText = e.clipboardData.getData('text/plain');
          const clipboardHtml = e.clipboardData.getData('text/html');
          ownerInstance.callMethod('onPaste', { id: this._eid, html: clipboardHtml, text: clipboardText })
        },
        onDrop: (e) => {
          // 编辑器内容拖动
          const dropText = e.dataTransfer.getData('text/plain');
          const dropHtml = e.dataTransfer.getData('text/html');
          ownerInstance.callMethod('onDrop', { id: this._eid, html: dropHtml, text: dropText })
        },
        onContentError: ({ editor, error, disableCollaboration }) => {
          // 内容不符合模式
          ownerInstance.callMethod('onContentError', { id: this._eid, error, disableCollaboration })
        }
      })

    },
  }
}
</script>

<style lang="scss">
@import '../icons/iconfont.css';

.sv-tiptap {
  --editor-bgcolor: #f1f1f1;
  --editor-border: 1px solid #cccccc;
  --editor-border-radius: 20rpx;
  --editor-padding: 12rpx 16rpx;

  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;

  .sv-tiptap-container {
    flex-grow: 1;
    border: var(--editor-border);
    overflow: auto;

    :deep(.tiptap) {
      width: 100%;
      height: 100%;
      overflow: auto;
      background-color: var(--editor-bgcolor);
      border-radius: var(--editor-border-radius);
      padding: var(--editor-padding);
      box-sizing: border-box;

      &.ProseMirror-focused {
        outline: none; // 取消默认聚焦边框样式
      }
    }
  }
}
</style>
